﻿using System;
using System.Collections.Generic;
using System.Text;
using chromium.Utils;

namespace chromium.HTTP.Parser
{
    public enum ParserState
    {
        Protocol,
        Header,
        Body,
        Ok
    } ;

    public class HttpRequestParser
    {
        private readonly Queue<HttpRequest> requests = new Queue<HttpRequest>();

        private readonly StringBuilder lineTemp = new StringBuilder();
        private ParserState parserState = ParserState.Protocol;
        private HttpRequest currentRequest;

        public HttpRequest CurrentRequest
        {
            get { return currentRequest; }
        }

        public ParserState ParserState
        {
            get { return parserState; }
        }

        public ParserState ParseReuest(byte[] buffer, int offset, int count)
        {
            int end = offset + count;
            int parsingIndex = offset;
            do
            {
                switch (parserState)
                {
                    case ParserState.Protocol:
                        if (!ReadLine(buffer, ref parsingIndex, end)) goto outter;

                        string[] line = lineTemp.ToString().Split(new char[] { ' ' }, 3);

                        currentRequest = new HttpRequest
                                             {
                                                 Method = line[0],
                                                 RawUrl = line[1],
                                                 Version = line[2]
                                             };
                        parserState = ParserState.Header;

                        lineTemp.Clear();
                        break;

                    case ParserState.Header:
                        if (!ReadLine(buffer, ref parsingIndex, end)) goto outter;

                        if (lineTemp.Length == 0)
                        {
                            parserState = ParserState.Body;
                            continue;
                        }

                        string[] header = lineTemp.ToString().Split(new char[] { ':' }, 2);
                        currentRequest.Headers.Add(header[0].Trim(), header[1].Trim());

                        lineTemp.Clear();
                        break;
                    case ParserState.Body:

                        if (!"POST".Equals(currentRequest.Method, StringComparison.OrdinalIgnoreCase))
                        {
                            requests.Enqueue(currentRequest);
                            parserState = ParserState.Protocol;
                            Statistic.AddParsingTime(currentRequest.ElapsedMs);
                            currentRequest = null;
                            continue;
                        }

                        int bytesToCopy = currentRequest.ContentLength - (int)currentRequest.RequestStream.Length;
                        int bytesLeft = end - parsingIndex;
                        int bytesAvailable = Math.Min(bytesLeft, bytesToCopy);

                        currentRequest.RequestStream.Write(buffer, parsingIndex, bytesAvailable);
                        parsingIndex += bytesAvailable;

                        //Extra data needed
                        if (bytesAvailable < bytesToCopy)
                            goto outter;

                        requests.Enqueue(currentRequest);
                        parserState = ParserState.Protocol;
                        Statistic.AddParsingTime(currentRequest.ElapsedMs);
                        currentRequest = null;

                        break;
                }
            } while (true);

        outter:
            return parserState;
        }

        private bool ReadLine(byte[] buffer, ref int parsingIndex, int end)
        {
            while (parsingIndex < end)
            {
                switch ((char)buffer[parsingIndex])
                {
                    case '\r':
                        parsingIndex++;
                        continue;
                    case '\n':
                        parsingIndex++;
                        return true;
                    default:
                        lineTemp.Append((char)buffer[parsingIndex]);
                        parsingIndex++;
                        break;
                }
            }
            return false;
        }

        public bool HasRequest()
        {
            return requests.Count > 0;
        }

        public HttpRequest GetRequest()
        {
            return requests.Dequeue();
        }

        internal string GetLineTemp()
        {
            return lineTemp.ToString();
        }
    }
}